package zcatt.examples.jface.snippets;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.util.Geometry;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.AnnotationModel;
import org.eclipse.jface.text.source.AnnotationPainter;
import org.eclipse.jface.text.source.IAnnotationAccess;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.text.source.inlined.AbstractInlinedAnnotation;
import org.eclipse.jface.text.source.inlined.InlinedAnnotationSupport;
import org.eclipse.jface.text.source.inlined.LineContentAnnotation;
import org.eclipse.jface.text.source.inlined.LineHeaderAnnotation;
import org.eclipse.jface.text.source.inlined.Positions;

import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.reconciler.DirtyRegion;
import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
import org.eclipse.jface.text.reconciler.MonoReconciler;
import org.eclipse.jface.window.ApplicationWindow;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.ColorDialog;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class Snippet011InlinedAnnotation extends ApplicationWindow 
{

	public static void main(String[] args) {
		Display display = new Display();
		
		ApplicationWindow appWin = new Snippet011InlinedAnnotation();
		appWin.setBlockOnOpen(true);
		appWin.create();
		appWin.getShell().setSize(600,400);
		appWin.open();		
		display.dispose();
	}

	SourceViewer sourceViewer;
	
	public Snippet011InlinedAnnotation() {		
		super(null);
	}


	@Override
	protected void configureShell(Shell shell) {
		super.configureShell(shell);
		shell.setText("Snippet011InlinedAnnotation");
	}

	@Override
	protected Control createContents(Composite parent) {
		Composite ret = (Composite) super.createContents(parent);
		ret.setLayout(new FillLayout());

		sourceViewer = new SourceViewer(ret, null, SWT.V_SCROLL | SWT.BORDER);
		sourceViewer.setDocument(new Document("\ncolor:rgb(255,255,0)"), new AnnotationModel());
		
		//connect sourceViewer with annPainter
		InlinedAnnotationSupport support= new InlinedAnnotationSupport();
		support.install(sourceViewer, createAnnPainter(sourceViewer));
		
		//connect sourceViewer with reconciler
		MonoReconciler reconciler = new MonoReconciler(new IReconcilingStrategy() {
			//zf, document changed, so update
			@Override
			public void setDocument(IDocument document) {	
				System.err.println("setDocument"+document.get());
				Set<AbstractInlinedAnnotation> anns = getInlinedAnnotation(sourceViewer, support);
				support.updateAnnotations(anns);
			}

			//zf, dirty occurs, so update
			@Override
			public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) {
				// sckip
				System.err.println("reconcile");
			}

			@Override
			public void reconcile(IRegion partition) {
				System.err.println("partition="+partition.getOffset());
				Set<AbstractInlinedAnnotation> anns = getInlinedAnnotation(sourceViewer, support);
				support.updateAnnotations(anns);			
			}
			
		}, false);		//zf, false导致调用第二个reconcile()
		
		reconciler.setDelay(1);
		reconciler.install(sourceViewer);
		
		return ret;
	}
	
	
	AnnotationPainter createAnnPainter(SourceViewer sviewer)
	{
		IAnnotationAccess annAccess = new IAnnotationAccess() {

			@Override
			public Object getType(Annotation annotation) {
				return annotation.getType();
			}

			@Override
			public boolean isMultiLine(Annotation annotation) {
				return true;
			}

			@Override
			public boolean isTemporary(Annotation annotation) {
				return true;
			}
			
		};
		AnnotationPainter ap = new AnnotationPainter(sviewer, annAccess);
		sviewer.addPainter(ap);
		return ap;
	}
	
	Set<AbstractInlinedAnnotation> getInlinedAnnotation(SourceViewer sviewer, InlinedAnnotationSupport support) {
		Set<AbstractInlinedAnnotation> anns = new HashSet<>();
		
		IDocument doc = sviewer.getDocument();
		int lineCount = doc.getNumberOfLines();
		for(int i = 0; i< lineCount; i++)
		{
			String line = getLineText(doc, i).trim();
			int index = line.indexOf("color:");
			if(index != 0)
				continue;
			
			String rgb = line.substring(index + "color:".length()).trim();
			try {
				Color color = parseColor(rgb);
				String status = (color != null) ? "OK!" : "ERR!";
				
				//status lineHeaderAnnnotation
				Position pos = Positions.of(i, doc, true);
				
				ColorStatusAnnotation statusAnn = support.findExistingAnnotation(pos);
				if(statusAnn == null)
					statusAnn = new ColorStatusAnnotation(pos, sviewer);
				statusAnn.setStatus(status);
				anns.add(statusAnn);
				
				//color square lineContentAnnotation
				if(color!= null)
				{
					Position colorPos = new Position(pos.offset + index + "color:".length(), 1);
					ColorAnnotation colorAnnotation = support.findExistingAnnotation(colorPos);
					if (colorAnnotation == null) {
						colorAnnotation = new ColorAnnotation(colorPos, sviewer);
					}
					colorAnnotation.setColor(color);
					anns.add(colorAnnotation);					
				}
				
				//rgb names lineContentAnnotation
				int rgbIndex = line.indexOf("rgb");
				if (rgbIndex != -1) {
					rgbIndex = rgbIndex + "rgb".length();
					int startOffset = pos.offset + rgbIndex;
					String rgbContent = line.substring(rgbIndex);
					int startIndex = addRGBParamNameAnnotation("red:", rgbContent, 0, startOffset, sviewer, support,
							anns);
					if (startIndex != -1) {
						startIndex = addRGBParamNameAnnotation("green:", rgbContent, startIndex, startOffset, sviewer,
								support, anns);
						if (startIndex != -1) {
							startIndex = addRGBParamNameAnnotation("blue:", rgbContent, startIndex, startOffset,
									sviewer, support, anns);
						}
					}
				}
				
			}
			catch(Exception e)
			{
				e.printStackTrace();
			}
		}
		
		return anns;
	}
	
	int addRGBParamNameAnnotation(String paramName, String rgbContent, int startIndex, int startOffset,
			ISourceViewer viewer, InlinedAnnotationSupport support, Set<AbstractInlinedAnnotation> annotations) {
		char startChar = startIndex == 0 ? '(' : ',';
		char[] chars = rgbContent.toCharArray();
		for (int i = startIndex; i < chars.length; i++) {
			char c = chars[i];
			if (c == startChar) {
				if (i == chars.length - 1) {
					return -1;
				}
				Position paramPos = new Position(startOffset + i + 1, 1);
				LineContentAnnotation colorParamAnnotation = support.findExistingAnnotation(paramPos);
				if (colorParamAnnotation == null) {
					colorParamAnnotation = new LineContentAnnotation(paramPos, viewer);
				}
				colorParamAnnotation.setText(paramName);
				annotations.add(colorParamAnnotation);
				return i + 1;
			}
		}
		return -1;
	}
	
	
	String getLineText(IDocument doc, int line)
	{
		try {
			int offset = doc.getLineOffset(line);
			int len = doc.getLineLength(line);
			return doc.get(offset, len);			
		}
		catch(Exception e)
		{
			e.printStackTrace();
			return null;
		}
	}
	
	Color parseColor(String str)
	{
		Pattern c = Pattern.compile("rgb *\\( *([0-9]+), *([0-9]+), *([0-9]+) *\\)");
		Matcher m = c.matcher(str);
		if (m.matches()) {
			try {
				return new Color(Integer.parseInt(m.group(1)), // r
						Integer.parseInt(m.group(2)), // g
						Integer.parseInt(m.group(3))); // b
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return null;		
	}
	
	class ColorStatusAnnotation extends LineHeaderAnnotation
	{

		public ColorStatusAnnotation(Position position, ISourceViewer viewer) {
			super(position, viewer);
		}

		public void setStatus(String status) {
			super.setText(status);
		}		
	}
	
	class ColorAnnotation extends LineContentAnnotation
	{
		Color color;
		private Consumer<MouseEvent> action = e -> {
			StyledText styledText = super.getTextWidget();
			Shell shell = new Shell(styledText.getDisplay());
			Rectangle location = Geometry.toDisplay(styledText, new Rectangle(e.x, e.y, 1, 1));
			shell.setLocation(location.x, location.y);
			// Open color dialog
			ColorDialog dialog = new ColorDialog(shell);
			// dialog.setRGB(annotation.getRGBA().rgb);
			RGB color = dialog.open();
			if (color != null) {
				// Color was selected, update the viewer
				try {
					int offset = getPosition().getOffset();
					IDocument document = getViewer().getDocument();
					IRegion line = document.getLineInformation(document.getLineOfOffset(offset));
					int length = line.getLength() - (offset - line.getOffset());
					String rgb = formatToRGB(color);
					document.replace(offset, length, rgb);
				} catch (BadLocationException e1) {
					e1.printStackTrace();
				}
			}
		};

		public ColorAnnotation(Position position, ISourceViewer viewer) {
			super(position, viewer);
		}
		
		public void setColor(Color color)
		{
			this.color = color;
		}
		
		
		@Override
		protected int drawAndComputeWidth(GC gc, StyledText textWidget, int offset, int length, Color color, int x,
				int y) {
			FontMetrics fontMetrics = gc.getFontMetrics();
			int squareSize = fontMetrics.getHeight() - 2*fontMetrics.getDescent();
			x += fontMetrics.getLeading();
			y += fontMetrics.getDescent();
			
			Rectangle rect = new Rectangle(x, y, squareSize, squareSize);
			
			gc.setBackground(this.color);
			gc.fillRectangle(rect);
			
			gc.setForeground(textWidget.getForeground());
			gc.drawRectangle(rect);
			
			int width = (int) (2 * fontMetrics.getAverageCharacterWidth() + squareSize);
			
			return width;			
		}

		String formatToRGB(RGB rgb) {
			return new StringBuilder("rgb(")
					.append(rgb.red).append(",")
					.append(rgb.green).append(",")
					.append(rgb.blue)
					.append(")").toString();
		}

		@Override
		public Consumer<MouseEvent> getAction(MouseEvent e) {
			return action;
		}		
	}
}
